﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;

namespace DeepTalkDBSerice
{
    // NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
    public class DTDBService : IDTDB
    {
        /// <summary>
        /// Returns the first maxtalk unrendered talks.
        /// </summary>
        /// <param name="maxtalks"></param>
        /// <returns></returns>
        public BasicTalkInfo[] GetUnrenderedTalks(int maxtalks)
        {
            try
            {
                using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
                {
                    var allc = from c in cont.ConferenceSet
                               where (c.InternalConferenceRenderState == (byte)Conference.ConferenceState.Unrendered) || (c.InternalConferenceRenderState == (byte)Conference.ConferenceState.Rendering)
                               orderby c.Id ascending
                               select c;

                    return IterToBasicTalk(allc, maxtalks);
                }
            }
            catch (Exception e)
            {
                _logger.LogMessage("Trying to get unrended talks", e);
                BasicTalkInfo[] result = new BasicTalkInfo[0];
                return result;
            }
        }

        /// <summary>
        /// Return a list of rendered talks, in date order.
        /// </summary>
        /// <param name="maxtalks"></param>
        /// <returns></returns>
        public BasicTalkInfo[] GetRenderedTalks(int maxtalks)
        {
            try
            {
                using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
                {
                    var allc = from c in cont.ConferenceSet
                               where (c.InternalConferenceRenderState == (byte) Conference.ConferenceState.Rendered) && (c.NumberOfTalks > 0)
                               orderby c.StartTime descending
                               select c;

                    return IterToBasicTalk(allc, maxtalks);
                }
            }
            catch (Exception e)
            {
                _logger.LogMessage("Attempting to get a list of rendered talks", e);
                BasicTalkInfo[] result = new BasicTalkInfo[0];
                return result;
            }
        }

        /// <summary>
        /// Returns a list of talks into an array, taking the first bits.
        /// </summary>
        /// <param name="allc"></param>
        /// <param name="maxtalks"></param>
        /// <returns></returns>
        private BasicTalkInfo[] IterToBasicTalk(IQueryable<Conference> allc, int maxtalks)
        {
            var asBasic = from c in allc
                          select new BasicTalkInfo()
                          {
                              Title = c.Title,
                              ID = c.Id,
                              StartDate = c.StartTime
                          };
            return asBasic.Take(maxtalks).ToArray();
        }


        /// <summary>
        /// Points to a logging facility - so we can write out our errors.
        /// </summary>
        private Logger _logger = new Logger();

        /// <summary>
        /// Points to the next free ID we can use with this DB.
        /// Null if not already initalized.
        /// </summary>
        private static int? _nextFreeID = null;

        /// <summary>
        /// Returns the next free ID integer for the database. Makes the assumption
        /// that we are the only ones to ever need to use the thing.
        /// </summary>
        private int nextFreeID
        {
            get
            {
                if (!_nextFreeID.HasValue)
                {
                    InitNextFreeID();
                }
                int temp = _nextFreeID.Value;
                _nextFreeID = _nextFreeID + 1;
                return temp;
            }
        }

        /// <summary>
        /// Scan the database for the next free integer that can be used to 
        /// add a new item to the list of talks.
        /// </summary>
        private void InitNextFreeID()
        {
            using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
            {
                var talkid = from c in cont.ConferenceSet
                             orderby c.Id descending
                             select c.Id;

                _nextFreeID = talkid.FirstOrDefault() + 1;
            }
        }

        /// <summary>
        /// Submit a new talk for rendering. If the talk is already present, then
        /// we just ignore the request.
        /// </summary>
        /// <param name="agendaInfo"></param>
        /// <param name="title"></param>
        /// <param name="startTime"></param>
        public void SubmitNewTalk(string agendaInfo, string title, DateTime startTime)
        {
            ///
            /// If the talk has already been inserted, then we can
            /// just bail!
            /// 

            if (FindTalk(agendaInfo) >= 0)
            {
                return;
            }

            ///
            /// Create the data and save it to the database
            /// 

            Conference c = new Conference();
            c.AgendaXML = agendaInfo;
            c.AgendaXMLHash = agendaInfo.GetHashCode();
            c.StartTime = startTime;
            c.Title = title;
            c.Id = nextFreeID;
            c.CurrentState = Conference.ConferenceState.Unrendered;

            try
            {
                using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
                {
                    cont.AddObject("ConferenceSet", c);
                    cont.SaveChanges();
                }
            }
            catch (Exception e)
            {
                _logger.LogMessage("Attempting to submit a new talk called " + title, e);
            }

            ///
            /// Notify the other half of our system that they should
            /// try to get this new conference rendered.
            /// 

            DTRender.Manager.ReScanForWork();
        }

        /// <summary>
        /// Return the unique ID for a talk. -1 if we can't find it.
        /// </summary>
        /// <param name="agendaInfo"></param>
        /// <returns></returns>
        public int FindTalk(string agendaInfo)
        {
            int myhash = agendaInfo.GetHashCode();
            try
            {
                using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
                {
                    var conferences = from c in cont.ConferenceSet
                                      where c.AgendaXMLHash == myhash
                                      select c;

                    ///
                    /// We can't use this as part of the SQL statement because the ntext type can't
                    /// really be used here. But since there are not likely to be many collisions, we
                    /// should be ok.
                    /// 

                    foreach (var c in conferences)
                    {
                        if (c.AgendaXML == agendaInfo)
                        {
                            return c.Id;
                        }
                    }
                    return -1;
                }
            }
            catch (Exception e)
            {
                _logger.LogMessage("Error trying to find an agenda in the DB", e);
                return -1;
            }
        }

        /// <summary>
        /// Checks the state of the talk - if it is in a state that can be viewed
        /// and served up by a web server, returns true.
        /// </summary>
        /// <param name="id"></param>
        public bool CanBeViewed(int id)
        {
            Conference c = Conference.FindTalkRO(id);
            return c.CurrentState == Conference.ConferenceState.Rendered;
        }

        /// <summary>
        /// Returns the end date/time for a talk
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public DateTime GetConferenceEndDate(int id)
        {
            Conference c = Conference.FindTalkRO(id);
            if (c.EndTime.HasValue)
            {
                return c.EndTime.Value;
            }
            else
            {
                return default(DateTime);
            }
        }

        /// <summary>
        /// Set the file we will use for our database. This is mainly for dealing
        /// with test routines -- so we can use a clean database or a pre-loaded
        /// database.
        /// </summary>
        /// <param name="info"></param>
        public void SetDBFile(FileInfo info)
        {
            DBUtilities.SetDBConnectionString("Data Source=" + info.FullName);

            ///
            /// Reset the next free integer -- who knows what has happened to this
            /// database before this!
            /// 

            _nextFreeID = null;
        }

        /// <summary>
        /// See if this conference exists...
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool ValidTalkID(int id)
        {
            try
            {
                Conference.FindTalkRO(id);
                return true;
            }
            catch
            {
            }
            return false;
        }

        /// <summary>
        /// Look for all talks that are marked in the "rendering" state and reset them to be unrendered.
        /// </summary>
        /// <returns>Number of talks we reset from this funny state</returns>
        public int[] ResetRenderingTalks()
        {
            using (ConferenceModelContainer cont = DBUtilities.GetConferenceContainer())
            {
                var confs = from c in cont.ConferenceSet
                            where c.InternalConferenceRenderState == (int) Conference.ConferenceState.Rendering
                            select c;

                int[] result = (from c in confs select c.Id).ToArray();

                foreach (var c in confs)
                {
                    c.CurrentState = Conference.ConferenceState.Unrendered;
                }
                cont.SaveChanges();

                return result;
            }
        }

        /// <summary>
        /// Reset a particular talk id to be unrendered.
        /// </summary>
        /// <param name="id"></param>
        public void ResetTalkStateToUnrendered(int id)
        {
            ManageTalkState.SetStateUnrendered(id);
        }
    }
}
